feat(Tearsheet): Implement new Tearsheet component - #954
Conversation
Generated-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
On Windows, Node's path.resolve and path.relative produce
backslash-separated paths (e.g. C:\src\*\index.ts). glob v10+
treats backslashes as escape characters rather than path separators,
so \* becomes a literal asterisk match instead of a wildcard. This
caused generate-index.js to find zero source files, producing an
empty src/index.ts, which compiled to an empty dist/esm/index.js.
The downstream build:fed:packages step then crashed with
"Cannot read properties of undefined (reading 'flags')" when the
TypeScript checker tried to get exports from a module with no symbol.
Even if the index had been populated, generate-fed-package-json.js
had the same glob issue — its patterns with process.cwd() backslashes
would match nothing on Windows, so no dist/dynamic/*/package.json
stubs would be created. The doc examples import from those stubs, so
the dev server would still show a blank page.
Additionally, path.relative on Windows returns backslash paths,
which broke the .replace('/dist', '') calls that strip the dist
prefix from relative paths written into generated package.json files.
Changes:
- Add packages/module/utils.js with cross-platform path utilities:
toPosixPath (normalize separators), posixGlobSync (normalize glob
pattern + results), and posixRelative (normalize path.relative
output). Each function is documented with why it exists.
- Refactor generate-index.js to use posixGlobSync from utils.js
- Refactor generate-fed-package-json.js to use toPosixPath,
posixGlobSync, and posixRelative from utils.js
- Normalize basePath and path.relative output inline in
scripts/parse-dynamic-modules.mjs (kept inline since it lives in
a separate shared scripts directory)
All changes are no-ops on Linux/macOS where paths already use
forward slashes.
Generated-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Doc site source discovery (patternfly-docs.source.js) passed raw path.join() output into globSync(). On Windows, path.join() produces backslash-separated paths (e.g. patternfly-docs\content\extensions\**\*.md). glob v10+ treats backslashes as escape characters, not path separators, so both the sourceProps and sourceMD globs silently matched zero files. This caused the generated/index.js routes file to be empty, leaving the entire Extensions sidenav blank on Windows dev servers. Fix: wrap glob patterns with the existing toPosixPath() utility from utils.js (created in d93b8fe for the same class of bug in other build scripts). Also mark className as optional in TearsheetBody, TearsheetFooter, and TearsheetHeader props interfaces — className is passed through to PF ModalBody/ModalFooter/ModalHeader which already default it. Generated-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Assisted-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
Tearsheet component: - Remove bottom border radius so tearsheet sits flush against viewport edge - Change tearsheetInner from height:100% to flex:1 1 auto + minHeight:0 so it participates properly in the ModalBox flex layout TearsheetBody component: - Replace placeholder caretColor:red with flex:1, minHeight:0, overflow:auto so the body fills remaining space and scrolls independently TearsheetGroup component: - Remove placeholder caretColor:red style Documentation (Tearsheet.md): - Expand propComponents to include TearsheetHeader, TearsheetBody, TearsheetFooter, and TearsheetGroup - Add descriptive text for each example section - Reorder examples: Basic, Layouts, Stacked, Group, Comparison TearsheetLayouts example: - Remove 'simple' layout, rename 'xl-text' to 'long-text' - Expand grid layout to 60 randomly sorted cards - Rename 'long' label to 'Flex layout' TearsheetComparison example (new): - Side-by-side Tearsheet vs Modal (ModalVariant.large) demo showing why tearsheets are better for dense content - Body content: sticky search bar (PageSection), vertical JumpLinks in a SidebarPanel, 3-column card grid with DescriptionLists, Labels, and CodeBlocks across 6 sections (36 cards total) - CSS fix for sidebar scrolling: sidebar__main height:100%, sidebar__content overflow:scroll + height:100% Generated-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
|
/deploy-preview |
There was a problem hiding this comment.
In addition to file comments below, we need to bump to the following package versions if they're being used in the component-groups package jsons:
- @patternfly/react-code-editor@6.6.2
- @patternfly/react-core@6.6.2
- @patternfly/react-docs@7.6.2
- @patternfly/react-drag-drop@6.6.2
- @patternfly/react-table@6.6.2
- @patternfly/react-templates@6.6.2
| # Sidenav top-level section | ||
| # should be the same for all markdown files | ||
| section: extensions | ||
| subsection: component-groups | ||
| # Sidenav secondary level section | ||
| # should be the same for all markdown files | ||
| id: Tearsheet | ||
| # Tab (react | react-demos | html | html-demos | design-guidelines | accessibility) | ||
| source: react | ||
| # If you use typescript, the name of the interface to display props for | ||
| # These are found through the sourceProps function provided in patternfly-docs.source.js |
There was a problem hiding this comment.
Let's remove these comments and just leave the property value pairs
| import { createUseStyles } from 'react-jss'; | ||
| import { Modal, ModalVariant } from '@patternfly/react-core'; | ||
|
|
||
| const useStyles = createUseStyles({ |
There was a problem hiding this comment.
@mcoker could you take a look at these new component files that are using useStyles? We likely can keep the styles inside these tsx files as there's convention in component-groups for it, so more if there's anything you think wew should tweak with the styles themselves.
| /** Accessible label for the tearsheet. */ | ||
| 'aria-label'?: string; | ||
| /** ID of the element that labels the tearsheet. */ | ||
| 'aria-labelledby'?: string; | ||
| /** ID of the element that describes the tearsheet. */ |
There was a problem hiding this comment.
| /** Accessible label for the tearsheet. */ | |
| 'aria-label'?: string; | |
| /** ID of the element that labels the tearsheet. */ | |
| 'aria-labelledby'?: string; | |
| /** ID of the element that describes the tearsheet. */ | |
| /** Accessible name for the tearsheet as a human readable string. */ | |
| 'aria-label'?: string; | |
| /** Space separated list of ID's of the elements that label the tearsheet. */ | |
| 'aria-labelledby'?: string; | |
| /** ISpace separated list of ID's of the elements that describe the tearsheet. */ |
| /** Flag to disable focus trap. */ | ||
| disableFocusTrap?: boolean; |
There was a problem hiding this comment.
What do you think about instead of exposing this prop specifically for now, we just include a modalProps prop that cna be spread on Modal? Just wondering if we should explicitly expose this one for now since having no focus trap with multiple modals open might be even more hairy than just a single, non-focus trapped modal.
|
|
||
| return ( | ||
| <Modal | ||
| // animated // Depends on animated modal PR being merged in |
There was a problem hiding this comment.
Can we remove this comment for now. Once the animation updates are done in core and your React PR can pull those in/be updated and merged, we can open a followup PR to add animation support in this repo
| height: 'calc(100% - 4rem) !important', | ||
| maxHeight: 'calc(100% - 4rem) !important', | ||
| insetBlockStart: '2rem !important', | ||
| // top: '2rem !important', |
There was a problem hiding this comment.
Are these top styles only needed once animation support is available? If so, could we remove them and instead either mention in your React PR or in this one just mentioning what top/additional styles will be needed for animation support?
| export interface TearsheetBodyProps extends ModalBodyProps { | ||
| className?: string; | ||
| } |
There was a problem hiding this comment.
We should include the children prop here and also provide descriptions. This will apply to the TearsheetFooter and TearsheetHeader component files as well.
| }, | ||
| }); | ||
|
|
||
| /** The maximum number of visually distinct stack levels (0, 1, 2). */ |
There was a problem hiding this comment.
Nit: we don't need this comment
| tearsheetGroup: { | ||
| }, |
There was a problem hiding this comment.
Do we plan to eventually add styles here, or is this more of an in case we ever decide to add any?
| /** Additional classes added to the Tearsheet group. */ | ||
| className?: string; | ||
| /** Unique id for the Tearsheet group. */ | ||
| id: string; |
There was a problem hiding this comment.
Would we be able to provide a random ID if a consumer doesn't pass one, or is it important for consumers to pass a static, unique ID?
kaylachumley
left a comment
There was a problem hiding this comment.
Thanks for working on this!
Left a bunch of content suggestions to match the patternfly voice and tone. @thatblindgeye feel free to chime in on any of those suggestions.
Also wanted to note that it looks like the tearsheets are using an outdated version of the "x" close icon button and needs to be swapped to the rh-icon equivalent.
Left a design recommendation as well. Tried to review as best I could, but just wanted to make note that if we can, lets make sure to use pf styling and no custom out of the box css (i didnt see anything crazy but just a general friendly note) 🤓
|
|
||
| ### Basic | ||
|
|
||
| Typical tearsheets should make use of the entire area, for this basic case some sample text is rendered. |
There was a problem hiding this comment.
| Typical tearsheets should make use of the entire area, for this basic case some sample text is rendered. | |
| Basic tearsheets should make use of the entire container. For this basic example, heading and body text is rendered with an action list placed within the footer area. |
|
|
||
| ### Tearsheet layouts | ||
|
|
||
| Tearsheets should allow various sorts of layouts to be rendered. |
There was a problem hiding this comment.
| Tearsheets should allow various sorts of layouts to be rendered. | |
| Tearsheets can be customized to render various layout styles. These layout styles include, [full width text](https://www.patternfly.org/foundations-and-styles/utility-classes/alignment), [flex](https://www.patternfly.org/foundations-and-styles/layouts/flex), and [grid](https://www.patternfly.org/foundations-and-styles/layouts/grid). |
| One special use case with tearsheets is stacking. | ||
| When a user is using a tearsheet, if another one needs to open it can open one level "on-top" of it in a new stack. | ||
| Tearsheets offer 3 stack levels (0,1,2). | ||
| A special stack level -1 allows a tearsheet to hide behind others. |
There was a problem hiding this comment.
| One special use case with tearsheets is stacking. | |
| When a user is using a tearsheet, if another one needs to open it can open one level "on-top" of it in a new stack. | |
| Tearsheets offer 3 stack levels (0,1,2). | |
| A special stack level -1 allows a tearsheet to hide behind others. | |
| Tearsheets support stacking, allowing new sheets to open on top of active ones. They utilize three visible stack levels (0, 1, and 2) and a background level (-1) to hide inactive sheets. |
| ### Tearsheet group (infinite stacking) | ||
|
|
||
| Use a `TearsheetGroup` to manage an unbounded number of stacked tearsheets. | ||
| `children` rendering order determines stacking priority with later children stacking in front of earlier ones. | ||
| Only the top 3 open tearsheets are visible; earlier ones hide behind the stack and reappear as front tearsheets are closed. | ||
|
|
||
| ```ts file="./TearsheetGroup.tsx" |
There was a problem hiding this comment.
From a design standpoint, I don't think its a good idea to recommend infinite stacking. I would advise against including this as an example so we dont encourage users to stack more than 4 sheets. I think it could be useful to include some explicit text in the stacking section that says something like "Limit stacked tearsheets to a maximum of three. Flows requiring four or more levels should be redesigned using multi-step wizard or dedicates page to prevent loss of user context."
Hiding an unbounded number of sheets behind the visible top 3 tends to break the user's mental model of where they are on the page, and forces "click-to-close" repetition to get back to the main view.
| ``` | ||
|
|
||
| ### Tearsheets vs Modals | ||
|
|
There was a problem hiding this comment.
| Choose a tearsheet over a modal when users need to process detailed and complex workflows. This example demonstrates how the expanded surface area of a tearsheet allows users to easily navigate a robust layout without feeling overwhelmed. |
mcoker
left a comment
There was a problem hiding this comment.
Left some comments but it looks good to me as long as you want to lock the full-page and stacked offset styles down and not allow overrides.
| maxWidth: 'calc(100% - 4rem) !important', | ||
| height: 'calc(100% - 4rem) !important', | ||
| maxHeight: 'calc(100% - 4rem) !important', | ||
| insetBlockStart: '2rem !important', |
There was a problem hiding this comment.
It seems like the only reason for the insetBlockStart is because the modal is center aligned vertically via align-items: center; on the parent. If you update this element to align-self: end;, you shouldn't need the insets on any of the tearsheet classes. And I might override align-self anyways because position="top" will apply align-self: flex-start and could break the tearsheet styling if applied. If you did that, I would still set insetBlockStart to auto here (instead of removing it) because position="top" also applies an inset-block-start you'll want to override.
| insetBlockStart: '2rem !important', | |
| insetBlockStart: 'auto !important', | |
| alignSelf: 'end !important' |
|
|
||
| const useStyles = createUseStyles({ | ||
| tearsheetHeader: { | ||
| flexShrink: 0, |
There was a problem hiding this comment.
This already comes with <ModalHeader />
|
|
||
| const useStyles = createUseStyles({ | ||
| tearsheetFooter: { | ||
| flexShrink: 0, |
There was a problem hiding this comment.
| tearsheetBody: { | ||
| flex: 1, | ||
| minHeight: 0, | ||
| overflow: 'auto', | ||
| }, |
There was a problem hiding this comment.
These are pretty similar to what comes with ModalBody, with the primary difference being ModalBody maintains a super small min-height in case you shrink the window super small, you could still manage to see some of the body text and it hides x-axis overflow where this style allows it with a scrollbar.
| // animated // Depends on animated modal PR being merged in | ||
| className={css(classes.tearsheet, stackLevelClassname)} | ||
| isOpen={isOpen} | ||
| variant={ModalVariant.large} |
There was a problem hiding this comment.
Not sure if you meant to include this? I'd remove it since it doesn't do anything.
Issues
Summary
Implementation of Tearsheet as a set of components in react-component-groups
Changes
<Tearsheet>,<TearsheetBody>,<TearsheetFooter>,<TearsheetHeader>, and<TearsheetGroup><Tearsheet>packages/module/utils.js(Common Windows support for paths)packages/module/generate-fed-package-json.jspackages/module/generate-index.jspackages/module/patternfly-docs/patternfly-docs.source.jsscripts/parse-dynamic-modules.mjsScreenshots